# coding: utf-8
#

import abc
from logging import exception
from multiprocessing import Value
from remote.settings import TEST

import uiautomator2
from uiautomator2.exceptions import BaseError
from remote.errors import OperationError
import time
from datetime import datetime
import uiautomator2 as u2
from uiautomator2 import Device
from logzero import logger
from .app_names import WECHAT, ANJUKE
from remote.wechat.operators import App, Contact, GroupRoom
from remote.enums import TaskStatus
from .enums import JobType
from threading import RLock
import copy
from .settings import server_setting

class TaskBase(metaclass=abc.ABCMeta):
  """表示执行一个特定app中的特定任务的类。"""
  def __init__(self, job_id: str, job_type: str, device_serial: str, task_callback = None, operator_data:dict = None):
    """表示执行一个特定app中的特定任务的类。
        Args: 
            app: 表示一个app的对象
    """
    super().__init__()
    #单链表结构的下一个元素
    self._next = None
    self._job_id = job_id
    self._job_type = job_type
    self._device_serial = device_serial
    self._plan_datetime = datetime.min
    self._task_callback = task_callback
    self._app = None
    self._operator_data = operator_data
    self._cached_data = dict()
    self._actual_executing_datetime = None
    self._actual_stopped_datetime = None
    self._status = None
    
  def __hash__(self) -> int:
    """重写hash算法。"""
    return hash(self._job_id) + hash(self._plan_datetime)

  
  def execute(self):
    try:
      if(self._status != TaskStatus.App_Initialed):
          # 必要的准备工作，在开始操作前。
          # Connect the device
          # wait the homepage of wechat rend
          self.prepare()
      self._inner_execute()
    except RuntimeError as e:
      self._status = TaskStatus.Device_Connecting_Error
      logger.warning("当前执行任务为: [%s]-[%s], 连接与%s设备出现错误: %s", \
        self._job_id, self._plan_datetime, self._device_serial, e)
    except ConnectionError as e:
      self._status = TaskStatus.Device_Connecting_Error
      logger.warning("当前执行任务为: [%s]-[%s], 连接与%s设备出现错误: %s", \
        self._job_id, self._plan_datetime, self._device_serial, e)
    except OperationError as e:
      self._app.stop()
      self._status = TaskStatus.Task_Completed_Error
      logger.warning("当前执行任务为: [%s]-[%s], 运行与%s设备上, 执行添加一个好友的自动化处理任务, 操作出现错误: %s", \
        self._job_id, self._plan_datetime, self._device_serial, e)
    # TODO: Replace the Customized Exception here, or create a new Exception Handler to handle the application errors.
    except Exception as e:
      self._status = TaskStatus.Task_Completed_Error
      logger.warn("结束-当前执行任务为: [%s]-[%s], 运行与%s设备上, 执行添加一个好友的自动化处理任务。操作出现错误: %s", \
        self._job_id, self._plan_datetime, self._device_serial, e)
    else:
      logger.info("结束-当前执行任务为: [%s]-[%s], 运行与%s设备上, 执行添加一个好友的自动化处理任务。", \
        self._job_id, self._plan_datetime, self._device_serial)
    finally:
      # 无论如何需要同步任务状态。
      self._inner_update_status()
      if(self._task_callback != None):
          self._task_callback(self)

  @abc.abstractmethod
  def prepare(self):
    """
      准备task以便后续的执行。
    """
    pass

  @abc.abstractmethod
  def check_operator_data(self, operator_data: dict) -> bool:
    pass
  
  @abc.abstractclassmethod
  def _inner_execute(self):
    pass

  @abc.abstractclassmethod
  def _inner_update_status(self):
    pass

  @abc.abstractclassmethod
  def _extract_cached_data(self):
    pass

  @abc.abstractclassmethod
  def _extract_operator_data(self):
    pass

  @property
  def job_id(self):
    return self._job_id

  @property
  def job_type(self):
    return self._job_type

  @property
  def device_serial(self):
    return self._device_serial

  @property
  def plan_datetime(self):
    return self._plan_datetime

  @plan_datetime.setter
  def plan_datetime(self, value: datetime):
    self._plan_datetime = value

  @property
  def next(self):
    return self._next

  @next.setter
  def next(self, value):
    self._next = value

  @property
  def cached_data(self):
    return self._cached_data
  
  @cached_data.setter
  def cached_data(self, value):
    self._cached_data = value
    self._extract_cached_data()
  
  @property
  def operator_data(self):
    return self._operator_data
  
  @operator_data.setter
  def operator_data(self, value):
    self._operator_data = value
    self._extract_operator_data()

  @property
  def status(self) -> TaskStatus:
    return self._status

  @status.setter
  def status(self, value):
    self._status = value
  
  def get_sjob_id(self):
    '''获得 scheduler Job ID'''
    return self._job_id + "-" + self._plan_datetime.strftime("%Y-%m-%d %H:%M:%S")
    
class AddFriendsFromGroupTask(TaskBase):
  '''负责执行和控制微信从群里面加好友的任务'''
  def __init__(self, job_id: str, job_type: str, device_serial: str, operator_data:dict, task_callback = None, ):
    super().__init__(job_id, job_type, device_serial, task_callback, operator_data)
    self._device = None
    self._current_group_index = 0
    self._current_member_index = 0
    self._cached_groups = list()
    self._cached_members = dict()
    #只包含之前收集过的群成员
    if self.check_operator_data(operator_data):
      self._current_group_index = operator_data["current_group_index"]
      self._current_member_index = operator_data["current_member_index"]
    else: 
        raise OperationError
        
    if("cached_groups" in self._cached_data.keys()):
      self._cached_groups = self._cached_data["cached_groups"]

    if("cached_members" in self._cached_data 
        and not self._cached_data["cached_members"] is None):
      self._cached_members = self._cached_data["cached_members"]
    else:
      self._cached_members = dict()

  @property
  def cached_groups(self) -> list:
    return self._cached_groups

  @property
  def cached_members(self) -> dict:
    return self._cached_members

  @property
  def plan_datetime(self) -> datetime:
    return self._plan_datetime
  
  @plan_datetime.setter
  def plan_datetime(self, value: datetime):
    self._plan_datetime = value

  def prepare(self):
    '''

    '''
    if self.status != TaskStatus.App_Initialed:
      self._device = u2.connect(self._device_serial)
      self._lanuch_app()
      logger.info("Job_id [%s] 与 %s 开始初始化： Task初始化，连接设备 [%s] 成功。预计第一次执行时间", self._job_id, self._plan_datetime, self._device_serial)
    self._status = TaskStatus.App_Initialed
      

  def _inner_execute(self):
    
    logger.info("开始-当前执行任务为: [%s]-[%s], 运行与%s设备上, 执行[group_index = %s and member_index = %s ]添加一个好友的自动化处理任务。", 
      self._job_id, self._plan_datetime, self._device_serial, self._current_group_index, self._current_member_index)
    self._status = TaskStatus.Task_Excuting
    self._do_operation()
    # 同步任务状态和数据
    self._status = TaskStatus.Task_Completed
    self._update_operator_data()
    self._update_cached_data()

  def _do_operation(self):
    app = self._app
    group_room = GroupRoom(app)
    contact = Contact(app)

    # 进入首页
    if(self._app.is_homepage()):
        group_room.navigate_contacts()
        time.sleep(0.5)

    # 进入通讯录
    if(app.is_contactbook()):
        group_room.navigate_groups()
        time.sleep(0.5)
    
    #进入群聊列表
    if(app.is_contact_chatroom()):
      #无论refresh是否为True，在内部list为空的情况下依旧会执行cache
      refresh = True if (len(self._cached_groups) <= 0) else False
      if not refresh:
        group_room.groups = self._cached_groups
      self._cached_groups = group_room.collect_groups(refresh)
      
      if self._is_device_completed():
        self.status = TaskStatus.Device_Completed
        logger.warning("开始-当前执行任务为: [%s]-[%s], 运行与%s设备上, 指定的group_index达到最大数，设备全部任务完成。", self._job_id, self._plan_datetime, self._device_serial)

      if(self.status != TaskStatus.Device_Completed):
        group_room.navigate_chating(self._current_group_index)
        time.sleep(0.5)

    #进入聊天界面
    if(app.is_chating() and
       self.status != TaskStatus.Device_Completed):

      refresh = False
      if self._current_group_index in self._cached_members.keys():
        if (self._cached_members[self._current_group_index] is None 
              or len(self._cached_members[self._current_group_index]) <= 0):
          refresh = True
        else:
          #恢复数据状态
          contact.members = self._cached_members[self._current_group_index]
      self._cached_members[self._current_group_index] = contact.navigate_chat_info(refresh)
      time.sleep(0.5)

    # 进入聊天成员界面
    can_send = False
    if((app.is_chatroom_info() or app.is_room_member_info()) and
        self.status != TaskStatus.Device_Completed):
      count = len(self._cached_members[self._current_group_index])
      if(self._current_member_index > (count-1)):
        #直接退出
        logger.warning("开始-当前执行任务为: [%s]-[%s], 运行与%s设备上, 指定的群%s中的成员%s超出现有群边界，任务终止。", 
                      self._job_id, 
                      self._plan_datetime, 
                      self._device_serial, 
                      self._current_group_index, 
                      self._current_member_index)
        self._current_group_index += 1
        if(self._is_device_completed()):
          self.status = TaskStatus.Device_Completed
        self._current_member_index = 0
        can_send = False
      else:
        contact.navigate_member_info(self._current_member_index)
        time.sleep(0.5)

    # FOR TEST ONLY
    # can_send = TEST
    # 添加好友请求
    if(app.is_contact_info() and
       self.status != TaskStatus.Device_Completed and
       can_send):
        contact.send_friend_invitation()

    # 恢复到操作流程开始位置
    if app.is_chatroom_info():
      app.tap_back()
    if app.is_room_member_info():
      app.tap_back()
    if app.is_chatroom_info():
      app.tap_back()
    if app.is_chating():
      app.tap_back()
    if app.is_contact_chatroom():
      logger.info("任务已返回到群聊界面")


    if self._current_group_index in self._cached_members.keys() and \
        not self._cached_members[self._current_group_index] is None and \
          self._current_member_index == (len(self._cached_members[self._current_group_index]) - 1):
      self._current_group_index += 1
      self._current_member_index = 0
    else:
      self._current_member_index += 1

  def _extract_cached_data(self):
    '''
      重写 super._extract_cached_data
    '''
    if("cached_groups" in self._cached_data.keys()):
      self._cached_groups = self._cached_data["cached_groups"]

    if("cached_members" in self._cached_data 
        and not self._cached_data["cached_members"] is None):
      self._cached_members = self._cached_data["cached_members"]

  def _extract_operator_data(self):
    '''
      重写 super._extract_operator_data
    '''
    if self.check_operator_data(self._operator_data):
      self._current_group_index = self._operator_data["current_group_index"]
      self._current_member_index = self._operator_data["current_member_index"]

  def _is_device_completed(self) -> bool:
    count = len(self._cached_groups)
    return True if self._current_group_index > count else False
    
  def _update_operator_data(self):
    self.operator_data["current_group_index"] = self._current_group_index
    self.operator_data["current_member_index"] = self._current_member_index

  def _update_cached_data(self):
    self._cached_data["cached_groups"] = self._cached_groups
    self._cached_data["cached_members"] = self._cached_members

  def _lanuch_app(self):
    self._app = App(self._device)
    self._app.start(True)
    self._app.wait_homepage()

  def check_operator_data(self, operator_data: dict) -> bool:
    return (not operator_data is None) and \
      "current_group_index" in operator_data.keys() and \
         "current_member_index" in operator_data.keys()
       
class AddFriendsFromExcelTask(TaskBase):
  '''负责执行和控制从excel里面加好友的任务'''
  def __init__(self, job_id: str, job_type: str, device_serial: str, operator_data:dict, task_callback = None, ):
    super().__init__(job_id, job_type, device_serial, task_callback, operator_data)
    self._device = None
    self._current_phone_index = 0
    self._current_phone = None
    self._cell_phones = list()
    #只包含之前收集过的群成员
    if self.check_operator_data(operator_data):
      self._current_phone_index = operator_data["current_phone_index"]
      self._cell_phones = operator_data['cellphones']
      self._current_phone = self._cell_phones[self._current_phone_index]

      # self._executing_result = True
    else: 
      raise OperationError("operator_data参数错误，缺少 current_phone_index 或 cellphones键")
  
  @property
  def current_phone(self):
    self._current_phone = self._cell_phones[self._current_phone_index]
    return self._current_phone

  def prepare(self):
    '''
    重写prepare方法。连接设备并启动app.
    '''
    if self.status != TaskStatus.App_Initialed:
      self._device = u2.connect(self._device_serial)
      self._lanuch_app()
      self._status = TaskStatus.App_Initialed
      logger.info("Job_id [%s] 与 %s 开始初始化： Task初始化，连接设备 [%s] 成功。预计第一次执行时间", self._job_id, self._plan_datetime, self._device_serial)

  def _inner_execute(self):
    # self._current_phone = self._cell_phones[self._current_phone_index]
    logger.info("开始-当前执行任务为: [%s]-[%s], 运行与%s设备上, 执行[phone_index = %s and phone_number = %s ]添加一个好友的自动化处理任务。", 
    self._job_id, self._plan_datetime, self._device_serial, self._current_phone_index, self.current_phone)
    app = self._app
    group_room = GroupRoom(app)
    contact = Contact(app)

    # 进入首页
    if(app.is_homepage()):
      group_room.navigate_contacts()
      time.sleep(0.5)
    if app.is_contactbook():
      group_room.navigate_new_friends()
      time.sleep(0.5)
    if(app.is_add_more_friend()):
      group_room.entry_search()
      time.sleep(0.5)

    #假定用户存在
    user_no_exists = False
    if(app.is_search_more_friends()):
      # 这将导致请求服务器，需要比较长的等待时间
      group_room.fillin_text(self.current_phone)
      time.sleep(1)
      group_room.select_search_result(self.current_phone)
      user_no_exists = group_room.check_search_result()
      #如果用户存在
      if not user_no_exists:
        cnt = 1
        while not app.is_contact_info() and cnt <= 3:
          logger.debug("等待联系人信息页面出现, 等待 %s秒", cnt * 0.5)
          time.sleep(cnt * 0.5)
          cnt += 1
    # FOR TEST ONLY
    can_send = server_setting.test # not TEST
    # 添加好友请求
    if not user_no_exists:
      if(app.is_contact_info() and \
        self.status != TaskStatus.Device_Completed):
        # 处理加好友过于频繁的情况
        # too_fast表示添加过于频繁。
        if can_send:
          too_fast = contact.send_friend_invitation()
          if too_fast:
            self._status = TaskStatus.Task_Limitation
            time.sleep(0.5)
      else:
        raise OperationError("联系人信息页面无法呈现")

    if app.is_contact_permission():
      # 这个界面原本会自动回到上一次，如果发现这个界面存在，
      # 可能需要处理微信请求过于频繁的情况。
      # ‘微信：操作过于频繁，请稍后再试。’
      app.tap_back()
      time.sleep(0.5)
      
    # 恢复到操作流程开始位置
    if app.is_contact_info():
      app.tap_back()
      time.sleep(0.5)
      
    if app.is_search_more_friends():
      group_room.fillin_text("")
      logger.info("返回到好友搜索界面")
    else:
      #如果返回好友搜索界面出现错误，直接重置app.
      self.prepare()
      logger.warn("返回到好友搜索界面出错，重置app。")

  def _inner_update_status(self):
    '''
      重写_inner_update_status方法。同步任务间的状态
    '''
    if self._current_phone_index + 1 < len(self._cell_phones):
      self._current_phone_index += 1
    else:
      self.status = TaskStatus.Device_Completed
    # update the status into operator_data.
    self._update_operator_data()

  def _lanuch_app(self):
    self._app = App(self._device)
    if not self._app.is_app_opened():
      self._app.start(True)
      self._app.wait_homepage()

  def check_operator_data(self, operator_data: dict) -> bool:
    return (not operator_data is None) and \
      "current_phone_index" in operator_data.keys() and \
        "cellphones" in operator_data.keys()

  def _update_operator_data(self):
    self.operator_data["current_phone_index"] = self._current_phone_index
    self.operator_data["cellphones"] = self._cell_phones
    self.operator_data["current_phone"] = self.current_phone
    self.operator_data["executing_result"] = False if self.status == TaskStatus.Task_Completed_Error else True

  def _extract_cached_data(self):
    '''

    '''
    logger.info("缓存数据不适用于AddFriendsFromExcelTask。")

  def _extract_operator_data(self):
    '''
      从operator_data中获取还原数据。
    '''
    self._cell_phones = self.operator_data["cellphones"]
    self._current_phone_index = self.operator_data["current_phone_index"]
    

class TaskFactory(object):
  ''' 任务工厂，根据Job的类型生成不同的Task。'''
  def _create_lock():
    """Creates a reentrant lock object."""
    return RLock()

  task_list = dict()
  locker = _create_lock()

  @staticmethod
  def create(app_name, job_type, job_id, device_serial, operator_data, task_callback) -> TaskBase:
    '''
      根据app_name 和 job_type来创建对应的 Task, 并保持同一类型的task实例只被创建一次。
      这样可以保留上一次操作保留下来的所有应用操作状态。
    '''
    task = None
      # 根据app_name 和 job_type确定创建相应的task.
    if(app_name == WECHAT and job_type == JobType.AddFriendInGroup.job_type):
      task = AddFriendsFromGroupTask(job_id, job_type, device_serial, operator_data, task_callback)
      TaskFactory.task_list[app_name + job_type] = task
    if (app_name == WECHAT and job_type == JobType.AddFriendFromExcel.job_type):
      task = AddFriendsFromExcelTask(job_id, job_type, device_serial, operator_data, task_callback)
    return task

  def map_task_status(self, task_status: dict):
    data = None
    if task_status['taskType'] == 'adfg':
      data = {
        'jobId':task_status['jobId'],
        'taskType':task_status['taskType'],
        'status':{
          'completedTaskLoad':task_status['completedTaskLoad'],
          'lastGroupIndex':task_status['optData']['current_group_index'],
          'lastMemberIndex':task_status['optData']['current_member_index']
          }
      }
    if task_status['taskType'] == 'adfe':
      opt_data = task_status['optData']
      data = {
        'jobId':task_status['jobId'],
        'taskType':task_status['taskType'],
        'status':{
          'completedTaskLoad':task_status['completedTaskLoad'],
          'phoneNumber': opt_data['current_phone'],
          'result':'completed' if opt_data['executing_result'] else 'recycled'
        }
      }
    return data

  def create_task_status(self, task:TaskBase) -> dict:
    status = { 
      "jobId":task.job_id,
      "taskType":task.job_type,
      "completedTaskLoad":0,
      "optData":task.operator_data,
      "cachedData":task.cached_data
    }
    return status

  @staticmethod
  def _create_singleton(app_name, job_type, job_id, device_serial, operator_data, task_callback) -> TaskBase:
    '''
      根据app_name 和 job_type来创建对应的 Task, 并保持同一类型的task实例只被创建一次。
      这样可以保留上一次操作保留下来的所有应用操作状态。
    '''
    task = None
    # 读锁
    with TaskFactory.locker:
      if (app_name + job_type) not in TaskFactory.task_list.keys():
        # 根据app_name 和 job_type确定创建相应的task.
        if(app_name == WECHAT and job_type == JobType.AddFriendInGroup.job_type):
          # 写锁
          with TaskFactory.locker:
            task = AddFriendsFromGroupTask(job_id, job_type, device_serial, operator_data, task_callback)
            TaskFactory.task_list[app_name + job_type] = task
      else:
        task = copy.copy(TaskFactory.task_list[app_name + job_type])
    return task


